////////////////////////////////////////////////////////////////////////////////////////////////////
//
//	Crytek Character Animation source code
//	
//	History:
//	28/09/2004 - Created by Ivo Herzeg <ivo@crytek.de>
//
//  Contains:
//  loading of model and animationss
/////////////////////////////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include <algorithm>

#include <I3DEngine.h>
#include <ICryAnimation.h>
#include <IIndexedMesh.h>
#include "VectorMap.h"
#include "CryHeaders.h"
#include "ModelMesh.h"
#include "Model.h"
#include "LoaderCHR.h"
#include "StringUtils.h"
#include "CharacterManager.h"
#include "FacialAnimation/FaceAnimation.h"
#include "FacialAnimation/FacialModel.h"
#include "FacialAnimation/FaceEffectorLibrary.h"
#include "LoaderDBA.h"
#include "AnimEventLoader.h"
#include "AnimationManager.h"
#include "Helper.h"

//////////////////////////////////////////////////////////////////////////
// temporary solution to access the links for thin and fat models
//////////////////////////////////////////////////////////////////////////
extern CCharacterModel* model_thin;
extern CCharacterModel* model_fat;

namespace CryCHRLoader_LoadNewCHR_Helpers {

static string GetLODName(const string& file, uint32 LOD) 
{
	return LOD ? file + "_lod" + CryStringUtils::toString(LOD) + "." + CRY_CHARACTER_FILE_EXT : file+"."+CRY_CHARACTER_FILE_EXT;
}

static bool ValidateLodSkeleton(CSkinningInfo* pSkinningInfo0, CSkinningInfo* pSkinningInfo1, int lod, const char* filename)
{
	uint32 numBones0 = pSkinningInfo0->m_arrBonesDesc.size();
	uint32 numBones1 = pSkinningInfo1->m_arrBonesDesc.size();
	if (numBones0!=numBones1) 
	{
		g_pISystem->Warning( VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE,filename, "Failed to Load Character, Different bone amount of LOD0 and LOD%i",lod );
		CryLog( "Hierarchy of LOD0" );
		for (uint32 bn=0; bn<numBones0; bn++)
			CryLog( "Joint ID : %02d %s",bn,pSkinningInfo0->m_arrBonesDesc[bn].m_arrBoneName );
		CryLog( "------------------------------------------------------------------" );
		CryLog( "Hierarchy of LOD%i",lod);
		for (uint32 bn=0; bn<numBones1; bn++)
			CryLog( "Joint ID : %02d %s",bn,pSkinningInfo1->m_arrBonesDesc[bn].m_arrBoneName );

		assert (0);
		return false;
	}

	uint32 HasLODMismatch=0;
	for (uint32 g=0; g<numBones0; g++)
	{
		const char* name0 = pSkinningInfo0->m_arrBonesDesc[g].m_arrBoneName;
		const char* name1 = pSkinningInfo1->m_arrBonesDesc[g].m_arrBoneName;
		bool IsIdentical  = ( 0 == stricmp(name0,name1) );
		if (IsIdentical==0) 
		{
			g_pISystem->Warning( VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE,filename,	"Character LOD mismatch. The bone number %d is different. LOD0 %s  LOD%i %s",g,name0, lod, name1 );
			assert (0);
			HasLODMismatch++;
		}
	}

	if (HasLODMismatch)
	{
		g_pISystem->Warning( VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE,filename, "Failed to Load Character, Different bone amount of LOD0 and LOD%i",lod );
		CryLog( "Hierarchy of LOD0" );
		for (uint32 bn=0; bn<numBones0; bn++)
			CryLogAlways( "Joint ID : %02d %s",bn,pSkinningInfo0->m_arrBonesDesc[bn].m_arrBoneName );
		CryLog( "------------------------------------------------------------------" );
		CryLog( "Hierarchy of LOD%i",lod);
		for (uint32 bn=0; bn<numBones1; bn++)
			CryLog( "Joint ID : %02d %s",bn,pSkinningInfo1->m_arrBonesDesc[bn].m_arrBoneName );
		return false;
	}
	return true;
}


static bool CreateMorphNameToIndexMap(VectorMap<string, int>& morphNameIndexMap, CContentCGF** cgfs, int lodCount, const char* filename)
{
	// Find the first model.
	CSkinningInfo* pBaseSkinningInfo = 0;
	for (int lod=0; lod<lodCount; lod++)
	{
		CContentCGF* pLOD = cgfs[lod];
		CSkinningInfo* pSkinningInfo = (pLOD ? pLOD->GetSkinningInfo() : 0);
		if (pSkinningInfo)
		{
			pBaseSkinningInfo = pSkinningInfo;
			break;
		}
	}

	assert(pBaseSkinningInfo);
	if (pBaseSkinningInfo == 0)
	{
		g_pISystem->Warning(VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE, filename, "Base skinned model is missing");
		return false;
	}

	// Build the name map.
	VectorMap<string, int>::container_type morphNames;
	uint32 morphCount = pBaseSkinningInfo->m_arrMorphTargets.size();
	for (uint32 morphIndex=0; morphIndex<morphCount; ++morphIndex)
	{
		const string& name = pBaseSkinningInfo->m_arrMorphTargets[morphIndex]->m_strName;
		// check for duplicate
		bool bExist = false;
		for(size_t i=0;i<morphNames.size();i++)
		{
			if( morphNames[i].first==name )
			{
				bExist = true;
				break;
			}
		}
		if( bExist )
		{
			g_pISystem->Warning( VALIDATOR_MODULE_ANIMATION, VALIDATOR_WARNING, VALIDATOR_FLAG_FILE,filename, "Duplicated morph mask found %s", name.c_str() );
		}
		morphNames.push_back(std::make_pair(name, morphIndex));
	}
	morphNameIndexMap.SwapElementsWithVector(morphNames);
	return true;
}

static bool ConvertTangentsToQTangents(CMesh* pMesh)
{
	Vec4sf* pTangents = (Vec4sf*)pMesh->m_pTangents;

	for (int e=0; e<pMesh->m_numVertices; e++)
	{















		Vec3 tangent(
			tPackB2F(pMesh->m_pTangents[e].Tangent.x),
			tPackB2F(pMesh->m_pTangents[e].Tangent.y),
			tPackB2F(pMesh->m_pTangents[e].Tangent.z));
		tangent.Normalize();

		Vec3 binormal(
			tPackB2F(pMesh->m_pTangents[e].Binormal.x),
			tPackB2F(pMesh->m_pTangents[e].Binormal.y),
			tPackB2F(pMesh->m_pTangents[e].Binormal.z));
		binormal.Normalize();

		f32 reflection = tPackB2F(pMesh->m_pTangents[e].Tangent.w);

		Matrix33 m;
		m.SetRow(0, tangent);
		m.SetRow(1, binormal);
		m.SetRow(2, tangent.Cross(binormal).GetNormalized());
		m.OrthonormalizeFast();

		uint32 isOrtho = m.IsOrthonormalRH(0.1f);
		if (isOrtho==0)
		{
			m.SetIdentity();  //hack to avoid a Division by 0 because of a broken tangent-matrix in the model-mesh
			//ANIM_ASSET_CHECK_TRACE(isOrtho != 0, ("Invalid tangent-matrix in model: %s, vertex position x=%f;y=%f;z=%f", strGeomFileName.c_str(), pMesh->m_pPositions[e].x, pMesh->m_pPositions[e].y, pMesh->m_pPositions[e].z));
		}

		Quat quat(m);
		quat.v = -quat.v;

		quat.Normalize();
		if (quat.w < 0.0f)
			quat = -quat;
		if (reflection < 0.0f)
		{
			static const float BIAS_16BIT = 1.0f / 32767.0f;
			static const float BIAS_SCALE_16BIT = sqrtf(1.0f - BIAS_16BIT*BIAS_16BIT);
			if (quat.w < BIAS_16BIT)
			{
				quat *= BIAS_SCALE_16BIT;
				quat.w = BIAS_16BIT;
			}
			quat = -quat;
		}

		int16f qx = tPackF2B(quat.v.x);
		int16f qy = tPackF2B(quat.v.y);
		int16f qz = tPackF2B(quat.v.z);
		int16f qw = tPackF2B(quat.w);







		pTangents[e].x = qx;
		pTangents[e].y = qy;
		pTangents[e].z = qz;
		pTangents[e].w = qw;

	}
	pMesh->m_pQTangents = (SMeshQTangents*)pMesh->m_pTangents;
	return true;
}

static bool InitializeBones(CModelSkeleton* pSkeleton, CSkinningInfo* pSkinningInfo, const char* filename)
{
	const int lod = 0;
	uint32 numBones = pSkinningInfo->m_arrBonesDesc.size();
	assert(numBones);
	if (numBones>MAX_JOINT_AMOUNT)
	{
		g_pISystem->Warning( VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE,filename,	"Too many Joints in model. Current Limit is: %d",MAX_JOINT_AMOUNT );
		return false;
	}

	if (!numBones)
		CryFatalError("ModelSkeleton joint count is 0");


	pSkeleton->m_poseData.AllocateData(numBones);
	pSkeleton->m_arrModelJoints.resize(numBones);


	for (uint32 nBone = 0; nBone < numBones; ++nBone) 
	{
		const CryBoneDescData& boneDesc = pSkinningInfo->m_arrBonesDesc[nBone];
		assert(boneDesc.m_DefaultB2W.IsOrthonormalRH());
		if (!boneDesc.m_DefaultB2W.IsOrthonormalRH())
		{
			g_pISystem->Warning(VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE, filename, "Bone %d: B2W is not orthonormal RH in lod %d", nBone, lod);
			return false;
		}

		CModelJoint& joint = pSkeleton->m_arrModelJoints[nBone];

		joint.m_idx							= nBone;

		joint.m_idxParent	= -1;
		int32 offset = boneDesc.m_nOffsetParent;
		if (offset)
			joint.m_idxParent = (int)nBone + offset;

		joint.m_nJointCRC32				=	boneDesc.m_nControllerID;
		joint.m_nJointCRC32Lower	=	g_pCrc32Gen->GetCRC32Lowercase(boneDesc.m_arrBoneName);
		joint.m_PhysInfo[0]				=	boneDesc.m_PhysInfo[0];
		joint.m_PhysInfo[1].pPhysGeom = 0;
		joint.m_fMass							=	boneDesc.m_fMass;
		pSkeleton->m_poseData.m_pJointsAbsolute[nBone] = QuatT(boneDesc.m_DefaultB2W);
		pSkeleton->m_poseData.m_pJointsAbsolute[nBone].q.Normalize();

		joint.m_nLimbId						=	boneDesc.m_nLimbId;
		joint.m_numChildren				=	boneDesc.m_numChildren;
		joint.m_nOffsetChildren		=	boneDesc.m_nOffsetChildren;

		pSkeleton->m_poseData.m_pJointsRelative[nBone] = pSkeleton->m_poseData.m_pJointsAbsolute[nBone];
		if (joint.m_idxParent >= 0)
		{
			pSkeleton->m_poseData.m_pJointsRelative[nBone] =
				pSkeleton->m_poseData.m_pJointsAbsolute[joint.m_idxParent].GetInverted() * pSkeleton->m_poseData.m_pJointsAbsolute[nBone];
		}
		pSkeleton->m_poseData.m_pJointsRelative[nBone].q.Normalize();

		joint.SetJointName( &boneDesc.m_arrBoneName[0] );
	}


	//check deepness-level inside hierarchy
	for (uint32 i=0; i<numBones; i++)
	{
		int32 parent = pSkeleton->m_arrModelJoints[i].m_idxParent;
		while (parent >= 0) 
		{
			pSkeleton->m_arrModelJoints[i].m_numLevels++;
			parent = pSkeleton->m_arrModelJoints[parent].m_idxParent;
		}
	}

	return true;
}

static bool FindFirstMeshAndMaterial(CMesh*& pMesh, _smart_ptr<IMaterial>& pMaterial, CContentCGF* pContent, const char* filenameNoExt, int lod)
{
	uint32 numNodes = pContent->GetNodeCount();
	CNodeCGF* pGFXNode=0;
	for(uint32 n=0; n<numNodes; n++) 
	{
		CNodeCGF* pNode = pContent->GetNode(n);
		if (pNode->type == CNodeCGF::NODE_MESH && pNode->pMesh )	
		{	
			pGFXNode = pNode;
			break; 
		}
	}
	if (pGFXNode==0)
	{
		g_pISystem->Warning( VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE,filenameNoExt,	"Failed to Load Character file. GFXNode not found" );
		return false;
	}

	pMesh = pGFXNode->pMesh;
	if (pMesh->m_pBoneMapping==0)
	{
		g_pISystem->Warning( VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE,filenameNoExt,	"Failed to Load Character file. Skeleton-Initial-Positions are missing" );
		return false;
	}

	if (lod == 0 || pMaterial == NULL)
	{
		if (pGFXNode->pMaterial)
			pMaterial = g_pI3DEngine->GetMaterialManager()->LoadCGFMaterial( pGFXNode->pMaterial,PathUtil::GetPath(filenameNoExt) );
		else
			pMaterial = g_pI3DEngine->GetMaterialManager()->GetDefaultMaterial();
	}
	return true;
}

static bool ExtractExternalVertices(DynArray<ExtSkinVertex>& arrExtVertices, const CModelMesh* pModelMesh, const CMesh* pMesh, const char* filename, int lod)
{
	arrExtVertices.resize(pModelMesh->m_numExtVertices);

	for (uint32 e=0; e<pModelMesh->m_numExtVertices; e++)
	{
		arrExtVertices[e].wpos1 = pMesh->m_pPositions ? pMesh->m_pPositions[e] : ToVec3(pMesh->m_pPositionsF16[e]);
		assert(arrExtVertices[e].wpos1.IsValid());
		if (!arrExtVertices[e].wpos1.IsValid())
		{
			g_pISystem->Warning(VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE, filename, "Invalid vertex %d in lod %d", e, lod);
			return 0;
		}
		arrExtVertices[e].u				=	pMesh->m_pTexCoord[e].s;
		arrExtVertices[e].v				=	pMesh->m_pTexCoord[e].t;

		if (pMesh->m_pShapeDeformation)
		{
			arrExtVertices[e].wpos0 = pMesh->m_pShapeDeformation[e].thin;
			arrExtVertices[e].wpos2 = pMesh->m_pShapeDeformation[e].fat;
			arrExtVertices[e].color = pMesh->m_pShapeDeformation[e].index;
		}

		arrExtVertices[e].boneIDs	=	pMesh->m_pBoneMapping[e].boneIDs;	
		arrExtVertices[e].weights	=	pMesh->m_pBoneMapping[e].weights;	
	}

	if (pMesh->m_streamSize[CMesh::QTANGENTS]) 
	{
		for (uint32 e=0; e<pModelMesh->m_numExtVertices; e++)
		{
			arrExtVertices[e].binormal=	pMesh->m_pQTangents[e].TangentBinormal;
		}
	}
	else 
	{
		for (uint32 e=0; e<pModelMesh->m_numExtVertices; e++)
		{
			arrExtVertices[e].binormal=	pMesh->m_pTangents[e].Binormal;
			arrExtVertices[e].tangent	=	pMesh->m_pTangents[e].Tangent;
		}
	}
	return true;
}

static bool CopyMorphTargets(CModelMesh* pModelMesh, CSkinningInfo* pSkinningInfo, const DynArray<ExtSkinVertex>& arrExtVertices, const VectorMap<string, int>& morphNameIndexMap, const char* filename, int lod)
{
	float fMidAxis = 0;
	// Calculate geometry vertical symetry line.
	for (uint32 e = 0; e <pModelMesh->m_numExtVertices; e++)
	{
		fMidAxis += arrExtVertices[e].wpos1.x;
	}
	fMidAxis = fMidAxis / pModelMesh->m_numExtVertices;

	uint32 numMorphtargets = pSkinningInfo->m_arrMorphTargets.size();
	pModelMesh->m_morphTargets.resize(morphNameIndexMap.size()); // All lods should have the same morph layout.
	std::fill(pModelMesh->m_morphTargets.begin(), pModelMesh->m_morphTargets.end(), 0);
	for(uint32 i=0; i<numMorphtargets; i++) 
	{
		// Look up the name->index map to find out where this morph target should go in the list.
		VectorMap<string, int>::const_iterator itNameMapPosition = morphNameIndexMap.find(pSkinningInfo->m_arrMorphTargets[i]->m_strName);
		if (itNameMapPosition == morphNameIndexMap.end())
			continue; // If the morph target doesn't exist in the base LOD, then we don't use it.
		int morphIndex = (*itNameMapPosition).second;

		CMorphTarget *pMorphTarget = new CMorphTarget;
		pModelMesh->m_morphTargets[morphIndex] = pMorphTarget;
		pMorphTarget->m_MeshID = pSkinningInfo->m_arrMorphTargets[i]->MeshID;
		pMorphTarget->m_name = pSkinningInfo->m_arrMorphTargets[i]->m_strName;

		fMidAxis = 0;

		AABB bbox;
		bbox.Reset();
		uint32 nVerts = (int)pSkinningInfo->m_arrMorphTargets[i]->m_arrExtMorph.size();
		assert(nVerts);
		if (nVerts <= 0)
		{
			g_pISystem->Warning(VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE, filename, "arrExtMorph[] is empty in lod %d", lod);
			return 0;
		}

		std::vector<Vec3> arrMTVertices; 
		arrMTVertices.resize(nVerts);

		pMorphTarget->m_vertices.resize(nVerts);
		pMorphTarget->m_MTNegBasis	=Vec3(+99999.0f,+99999.0f,+99999.0f);
		pMorphTarget->m_MTExtensions=Vec3(-99999.0f,-99999.0f,-99999.0f);
		for (uint32 v=0; v<nVerts; v++)
		{
			uint32 nVertexId = pSkinningInfo->m_arrMorphTargets[i]->m_arrExtMorph[v].nVertexId;
			assert(nVertexId<0xffff); //lets hope that no crazy artist will create a model with more then 0xffff vertices
			if (nVertexId >= 0xffff) 
			{
				g_pISystem->Warning(VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE, filename, "Vertex id 0x%x is too big in lod %d", nVertexId, lod);
				return 0;
			}
			pMorphTarget->m_vertices[v].m_nVertexId = nVertexId;
			arrMTVertices[v] = pSkinningInfo->m_arrMorphTargets[i]->m_arrExtMorph[v].ptVertex;

			if (pMorphTarget->m_MTNegBasis.x>arrMTVertices[v].x)
				pMorphTarget->m_MTNegBasis.x=arrMTVertices[v].x;
			if (pMorphTarget->m_MTNegBasis.y>arrMTVertices[v].y)
				pMorphTarget->m_MTNegBasis.y=arrMTVertices[v].y;
			if (pMorphTarget->m_MTNegBasis.z>arrMTVertices[v].z)
				pMorphTarget->m_MTNegBasis.z=arrMTVertices[v].z;

			if (pMorphTarget->m_MTExtensions.x<arrMTVertices[v].x)
				pMorphTarget->m_MTExtensions.x=arrMTVertices[v].x;
			if (pMorphTarget->m_MTExtensions.y<arrMTVertices[v].y)
				pMorphTarget->m_MTExtensions.y=arrMTVertices[v].y;
			if (pMorphTarget->m_MTExtensions.z<arrMTVertices[v].z)
				pMorphTarget->m_MTExtensions.z=arrMTVertices[v].z;

			// Calculate morph target bounding box.
			bbox.Add( arrExtVertices[nVertexId].wpos1 );
		}

		pMorphTarget->m_MTExtensions.x += -pMorphTarget->m_MTNegBasis.x;
		pMorphTarget->m_MTExtensions.x = max(pMorphTarget->m_MTExtensions.x,0.0001f);
		assert(pMorphTarget->m_MTExtensions.x);
		if (pMorphTarget->m_MTExtensions.x == 0)
		{
			g_pISystem->Warning(VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE, filename, "Vertex extension x is zero in lod %d", lod);
			return 0;
		}

		pMorphTarget->m_MTExtensions.y += -pMorphTarget->m_MTNegBasis.y;
		pMorphTarget->m_MTExtensions.y = max(pMorphTarget->m_MTExtensions.y,0.0001f);
		assert(pMorphTarget->m_MTExtensions.y);
		if (pMorphTarget->m_MTExtensions.y == 0)
		{
			g_pISystem->Warning(VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE, filename, "Vertex extension y is zero in lod %d", lod);
			return false;
		}

		pMorphTarget->m_MTExtensions.z += -pMorphTarget->m_MTNegBasis.z;
		pMorphTarget->m_MTExtensions.z = max(pMorphTarget->m_MTExtensions.z,0.0001f);
		assert(pMorphTarget->m_MTExtensions.z);
		if (pMorphTarget->m_MTExtensions.z == 0)
		{
			g_pISystem->Warning(VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE, filename, "Vertex extension z is zero in lod %d", lod);
			return false;
		}

		for (uint32 v=0; v<nVerts; v++)
		{
			arrMTVertices[v].x=(arrMTVertices[v].x-pMorphTarget->m_MTNegBasis.x)/pMorphTarget->m_MTExtensions.x;
			arrMTVertices[v].y=(arrMTVertices[v].y-pMorphTarget->m_MTNegBasis.y)/pMorphTarget->m_MTExtensions.y;
			arrMTVertices[v].z=(arrMTVertices[v].z-pMorphTarget->m_MTNegBasis.z)/pMorphTarget->m_MTExtensions.z;
		}

		//vertex quantizations
		for (uint32 v=0; v<nVerts; v++)
		{
			int32 x=int32(arrMTVertices[v].x*255.0f);
			x=max(x,0); 	x=min(x,255);
			int32 y=int32(arrMTVertices[v].y*255.0f);
			y=max(y,0);		y=min(y,255);
			int32 z=int32(arrMTVertices[v].z*255.0f);
			z=max(z,0);		z=min(z,255);
			pMorphTarget->m_vertices[v].m_MTVertexX = x;
			pMorphTarget->m_vertices[v].m_MTVertexY = y;
			pMorphTarget->m_vertices[v].m_MTVertexZ = z;
		}


		Vec3 bboxCenter = bbox.GetCenter();
		Vec3 bboxHalfSize = bbox.GetSize() * 0.5f;	

		for (uint32 v=0; v<nVerts; v++)
		{
			pMorphTarget->m_vertices[v].m_fBalanceLR = 0xff;
			assert( fabsf(bboxHalfSize.x)>0.001f );
			if (fabsf(bboxHalfSize.x) <= 0.001f)
			{
				g_pISystem->Warning(VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE, filename, "Bound box is too small in lod %d", lod);
				return false;
			}

			if (bboxHalfSize.x)
			{
				uint32 nVertexId = pMorphTarget->m_vertices[v].m_nVertexId;
				Vec3 point = arrExtVertices[nVertexId].wpos1;
				float balance = (point.x - bboxCenter.x) / bboxHalfSize.x;

				float fl = 1.0f - max(balance,0.0f);
				float fr = 1.0f - max(-balance,0.0f);
				fl = fl * fl * fl;
				fr = fr * fr * fr;

				int32 ufl=int32(fl*15.0f);
				ufl=max(ufl,0x00);
				ufl=min(ufl,0x0f);
				int32 ufr=int32(fr*15.0f);
				ufr=max(ufr,0x00);
				ufr=min(ufr,0x0f);
				pMorphTarget->m_vertices[v].m_fBalanceLR	= (uint8(ufr)<<4) | (uint8(ufl));
			}
		}
	}
	// remove NULL morphs entries
	for(int n=(int)pModelMesh->m_morphTargets.size()-1;n>=0;n--)
	{
		if( pModelMesh->m_morphTargets[n]==NULL )
		{
			pModelMesh->m_morphTargets.erase(pModelMesh->m_morphTargets.begin()+n);
		}
	}
	return true;
}

static bool SetupCollisionInfo(CCharacterModel* pModel, CModelMesh* pModelMesh, const CSkinningInfo* pSkinningInfo, DynArray<uint16> &arrExtToIntMap, bool isBaseLod)
{
	std::set<int> usedBoneslist;
	std::vector<int> useRemap(pSkinningInfo->m_arrBonesDesc.size());

	if (isBaseLod && !pSkinningInfo->m_bProperBBoxes)
	{
		for (uint32 e=0; e<pModelMesh->m_numExtVertices; e++)
		{
			uint32 i=arrExtToIntMap[e];
			uint8 idx0 = (uint8)pModelMesh->m_arrIntVertices[i].boneIDs[0];
			uint8 idx1 = (uint8)pModelMesh->m_arrIntVertices[i].boneIDs[1];
			uint8 idx2 = (uint8)pModelMesh->m_arrIntVertices[i].boneIDs[2];
			uint8 idx3 = (uint8)pModelMesh->m_arrIntVertices[i].boneIDs[3];
			usedBoneslist.insert(idx0);
			usedBoneslist.insert(idx1);
			usedBoneslist.insert(idx2);
			usedBoneslist.insert(idx3);
		}

		int remap = 0;
		for (std::set<int>::iterator it = usedBoneslist.begin(), end = usedBoneslist.end(); it != end ; ++it, ++remap) 	
		{
			MeshCollisionInfo meshCollision;
			meshCollision.m_aABB = AABB(Vec3(VMAX),Vec3(VMIN));
			meshCollision.m_iBoneId = *it;
			pModel->m_arrCollisions.push_back(meshCollision);
			useRemap[*it] = remap;
		}
	}

	pModelMesh->m_arrExtBoneIDs.resize(pModelMesh->m_numExtVertices);
	for (uint32 e=0; e<pModelMesh->m_numExtVertices; e++)
	{
		uint32 i=arrExtToIntMap[e];
		uint8 idx0 = (uint8)pModelMesh->m_arrIntVertices[i].boneIDs[0];
		uint8 idx1 = (uint8)pModelMesh->m_arrIntVertices[i].boneIDs[1];
		uint8 idx2 = (uint8)pModelMesh->m_arrIntVertices[i].boneIDs[2];
		uint8 idx3 = (uint8)pModelMesh->m_arrIntVertices[i].boneIDs[3];
		pModelMesh->m_arrExtBoneIDs[e].idx0	= idx0;
		pModelMesh->m_arrExtBoneIDs[e].idx1	= idx1;
		pModelMesh->m_arrExtBoneIDs[e].idx2	= idx2;
		pModelMesh->m_arrExtBoneIDs[e].idx3	= idx3;

		if (isBaseLod && !pSkinningInfo->m_bProperBBoxes)
		{
			pModel->m_arrCollisions[useRemap[idx0]].m_aABB.Add(pModelMesh->m_arrIntVertices[i].wpos1);
			pModel->m_arrCollisions[useRemap[idx1]].m_aABB.Add(pModelMesh->m_arrIntVertices[i].wpos1);
			pModel->m_arrCollisions[useRemap[idx2]].m_aABB.Add(pModelMesh->m_arrIntVertices[i].wpos1);
			pModel->m_arrCollisions[useRemap[idx3]].m_aABB.Add(pModelMesh->m_arrIntVertices[i].wpos1);

			pModel->m_arrCollisions[useRemap[idx0]].m_arrIndexes.push_back(e);
			pModel->m_arrCollisions[useRemap[idx1]].m_arrIndexes.push_back(e);
			pModel->m_arrCollisions[useRemap[idx2]].m_arrIndexes.push_back(e);
			pModel->m_arrCollisions[useRemap[idx3]].m_arrIndexes.push_back(e);
		}

	}
	return true;
}

static bool FinishSetupCollisionInfo(CCharacterModel* pModel, CMesh* pMesh, const CSkinningInfo* pSkinningInfo, const char* filename)
{
	int numExtFaces = pMesh->m_nIndexCount / 3;
	std::vector<TFace> arrExtFaces;
	arrExtFaces.resize(numExtFaces);
	for (int f=0; f<numExtFaces; f++)
	{
		arrExtFaces[f].i0 = pMesh->m_pIndices[f*3+0];
		arrExtFaces[f].i1 = pMesh->m_pIndices[f*3+1];
		arrExtFaces[f].i2 = pMesh->m_pIndices[f*3+2];
	}

	if(!pSkinningInfo->m_bProperBBoxes)
	{
		for (int i = 0; i < pModel->m_arrCollisions.size(); ++i)
		{
			std::set<int> unique;

			for (int j = 0; j < pModel->m_arrCollisions[i].m_arrIndexes.size(); ++j)
			{
				unique.insert(pModel->m_arrCollisions[i].m_arrIndexes[j]);
			}

			DynArray<short int> tmp;
			tmp.swap(pModel->m_arrCollisions[i].m_arrIndexes);
			pModel->m_arrCollisions[i].m_arrIndexes.reserve(unique.size());

			for (uint32 j = 0; j < arrExtFaces.size(); ++j)
			{
				if ((unique.find(arrExtFaces[j].i0) != unique.end()) || (unique.find(arrExtFaces[j].i1) != unique.end()) || (unique.find(arrExtFaces[j].i2) != unique.end()))
				{
					pModel->m_arrCollisions[i].m_arrIndexes.push_back(j);
					if((j * 3 + 2) >= static_cast<uint32>(pSkinningInfo->m_arrIntFaces.size() * 3))
					{
						g_pISystem->Warning( VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING,VALIDATOR_FLAG_FILE,"Error mapping face indices while loading CHR file %s", filename);
					}
				}
			}
		}
		// we don't need base data
		DynArray<short int> tmp;
		tmp.swap(pModel->m_arrCollisions[0].m_arrIndexes);
	}
	else
	{
		pModel->m_arrCollisions = pSkinningInfo->m_arrCollisions;
	}
	return true;
}

bool SetupShapeDeformation(CModelMesh* pModelMesh, CMesh* pMesh, DynArray<uint16> arrExtToIntMap, int numIntVertices, const char* filename, int lod)
{
#if !defined(SHAPE_DEFORMATION_DISABLED)
	//-----------------------------------------------------------------
	//---   check if we have a thin and a fat version               ---
	//---              this is a temporary solution                 ---
	//-----------------------------------------------------------------
	if (model_thin && model_fat) 
	{	
		//overwrite the precalculated thin and fat version
		CModelMesh* pSkin_thin = model_thin->GetModelMesh(lod);
		CModelMesh* pSkin_fat	= model_fat->GetModelMesh(lod);
		if (pSkin_thin==0 || pSkin_fat==0)
		{
			g_pISystem->Warning( VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE,filename,	"Failed to Load Character file. The basemodel has thin and fat version, but the LODs don't." );
			return false;
		}

		uint32 size_thin = pSkin_thin->m_arrIntVertices.size();
		uint32 size_fat  = pSkin_fat->m_arrIntVertices.size();
		if (numIntVertices!=size_thin ||  numIntVertices!=size_fat)
		{
			g_pISystem->Warning( VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE,filename,	"Failed to Load Character file. Thin, fat and basemodel have different vertex-count" );
			return false;
		}

		//	assert(numIntVertices==size_thin);
		//	assert(numIntVertices==size_fat);
		for(uint32 i=0; i<numIntVertices; i++)
		{
			pModelMesh->m_arrIntVertices[i].wpos0=pSkin_thin->m_arrIntVertices[i].wpos1;
			pModelMesh->m_arrIntVertices[i].wpos2=pSkin_fat->m_arrIntVertices[i].wpos1;
		}

		for (uint32 e=0; e<pModelMesh->m_numExtVertices; e++ )
		{
			uint32 i=arrExtToIntMap[e];
			pMesh->m_pShapeDeformation[e].thin	= pModelMesh->m_arrIntVertices[i].wpos0;
			pMesh->m_pShapeDeformation[e].fat		= pModelMesh->m_arrIntVertices[i].wpos2;
			pMesh->m_pShapeDeformation[e].index	= pModelMesh->m_arrIntVertices[i].color;
		}
	}
	else
		pMesh->m_pShapeDeformation = NULL;

#else
	pMesh->ReallocStream(CMesh::SHAPEDEFORMATION,0); // free not used but allocated SHAPEDEFORMATION data
	pMesh->m_pShapeDeformation = NULL;
#endif
	return true;
}

static bool SetupPhysicalProxies(IGeomManager* pPhysicalGeometryManager, CCharacterModel* pModel, IMaterial* pMaterial, const CSkinningInfo* pSkinningInfo, const char* filename, int lod)
{
	// this map contains the bone geometry. The mapping is : [Chunk ID]->[Physical geometry read from that chunk]
	// the chunks from which the physical geometry is read are the bone mesh chunks.
	CCharacterModel::ChunkIdToPhysGeomMap mapLimbPhysGeoms;
	mapLimbPhysGeoms.clear();
	uint32 numPBM = pSkinningInfo->m_arrPhyBoneMeshes.size();	
	int useOnlyBoxes=0; 





	for (uint32 p=0; p<numPBM; p++) 
	{
		PhysicalProxy  pbm = pSkinningInfo->m_arrPhyBoneMeshes[p];
		uint32 numFaces = pbm.m_arrMaterials.size();
		// add a new entry to the map chunk->limb geometry, so that later it can be found by the bones
		// during the post-initialization mapping of pPhysGeom from Chunk id to the actual geometry pointer
		uint32 flags = (numFaces<=20 ? mesh_SingleBB : mesh_OBB|mesh_AABB|mesh_AABB_rotated) | mesh_multicontact0 | mesh_approx_box | ((mesh_approx_sphere | mesh_approx_cylinder | mesh_approx_capsule) & (useOnlyBoxes-1));

		IGeometry* pPhysicalGeometry = pPhysicalGeometryManager->CreateMesh(&pbm.m_arrPoints[0],&pbm.m_arrIndices[0], &pbm.m_arrMaterials[0] ,0,numFaces,flags, (useOnlyBoxes ? 1.f : 0.05f));
		assert (pPhysicalGeometry);
		if (pPhysicalGeometry == 0)
		{
			g_pISystem->Warning(VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE, filename, "Physics: Failed to create phys mesh in lod %d", lod);
			return false;
		}
		assert(pMaterial!=0);
		if (pMaterial == 0)
		{
			g_pISystem->Warning(VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE, filename, "Physics: No material in lod %d", lod);
			return false;
		}
		if (pMaterial)
		{
			// Assign custom material to physics.
			int defSurfaceIdx = pbm.m_arrMaterials.empty() ? 0 : pbm.m_arrMaterials[0] ;
			int surfaceTypesId[MAX_SUB_MATERIALS];
			memset( surfaceTypesId,0,sizeof(surfaceTypesId) );
			int numIds = pMaterial->FillSurfaceTypeIds(surfaceTypesId);
			mapLimbPhysGeoms[pbm.ChunkID] =	pPhysicalGeometryManager->RegisterGeometry( pPhysicalGeometry, defSurfaceIdx, &surfaceTypesId[0],numIds );
		}
		pPhysicalGeometry->Release();
	}

	if (lod == 0)
	{
		pModel->m_bHasPhysics2 = false;
	}

	if (lod < 2)
	{
		// change the pPhysGeom from indices (chunk ids) to the actual physical geometry pointers
		// This modifies the given map by deleting or zeroing elements of the map that were used during the mapping
		if (pModel->PostInitBonePhysGeom (mapLimbPhysGeoms, lod))
		{
			pModel->UpdatePhysBonePrimitives(pSkinningInfo->m_arrBoneEntities, lod);
			pModel->m_bHasPhysics2 = true;
			pModel->onBonePhysicsChanged();
		}
	}

	// clean up the physical geometry objects that were not used
	for (CCharacterModel::ChunkIdToPhysGeomMap::iterator it = mapLimbPhysGeoms.begin(); it != mapLimbPhysGeoms.end(); ++it)
	{
		phys_geometry* pPhysGeom = it->second;
		if (pPhysGeom)
			g_pIPhysicalWorld->GetGeomManager()->UnregisterGeometry(pPhysGeom);
	}
	mapLimbPhysGeoms.clear();
	return true;
}

}

CCharacterModel* CryCHRLoader::LoadNewCHR( const string& strGeomFileName, CharacterManager* pManager, uint32 SurpressWarning, uint32 IsSkinAttachment, uint32 nLoadingFlags)
{
	using namespace CryCHRLoader_LoadNewCHR_Helpers;

	LOADING_TIME_PROFILE_SECTION(g_pISystem);

	COMPILE_TIME_ASSERT(sizeof(TFace)==6);

	MEMSTAT_CONTEXT_FMT(EMemStatContextTypes::MSC_CHR, 0, "LoadCharacter %s", strGeomFileName.c_str());

	CRY_DEFINE_ASSET_SCOPE( "CHR",strGeomFileName.c_str() );

	const char* szExt = CryStringUtils::FindExtension(strGeomFileName.c_str());
	m_strGeomFileNameNoExt.assign (strGeomFileName.c_str(), *szExt?szExt-1:szExt);

	if (m_strGeomFileNameNoExt.empty())
	{
		g_pISystem->Warning(VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE, strGeomFileName, "Wrong character filename");
		return 0;
	}

	//--------------------------------------------------------------------------------------------

	struct ContentCgfs
	{
		std::vector<CContentCGF*> arr;
		
		void release()
		{
			for (size_t i = 0; i < arr.size(); ++i) 
			{
				if (arr[i])
				{
					g_pI3DEngine->ReleaseChunkfileContent(arr[i]);
					arr[i] = 0;
				}
			}
			arr = std::vector<CContentCGF*>();
		}

		~ContentCgfs()
		{
			release();
		}
	} cgfs;

	cgfs.arr.resize(1);

	uint32 lodCount = 0;
	uint32 baseLOD = 0;

	stack_string filename = stack_string(m_strGeomFileNameNoExt.c_str()) + "." + CRY_CHARACTER_FILE_EXT;

	// try to load base lod
	while (true)
	{
		bool bNoWarnings = baseLOD != 0;
		const string& fileName = GetLODName(m_strGeomFileNameNoExt, baseLOD); 
		cgfs.arr[0] = g_pI3DEngine->CreateChunkfileContent(fileName); 
		bool bLoaded = g_pI3DEngine->LoadChunkFileContent(cgfs.arr[0], fileName, bNoWarnings);
		if (cgfs.arr[0] && bLoaded)
		{
			lodCount++;
			break;
		}
		else
		{
			if (baseLOD == 0)
			{
				if (SurpressWarning==0)
				{
					g_pISystem->Warning( VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE, filename, "Failed to Load Character file" );
				}
				return 0;
			}
			--baseLOD;
		}
	}

	uint32 assetDefinedLOD = 0;
	static ICVar* p_ca_useAssetDefinedLod = gEnv->pConsole->GetCVar("ca_UseAssetDefinedLod");
	const bool useAssetDefinedLOD = !p_ca_useAssetDefinedLod ? false : p_ca_useAssetDefinedLod->GetIVal() != 0;

	const bool ignoreLods = ((nLoadingFlags & IStatObj::ELoadingFlagsIgnoreLoDs) != 0);

	// load lods
	while (true)
	{
		bool bNoWarnings = (baseLOD + lodCount) != 0;
		if(bNoWarnings && ignoreLods)
		{
			break;
		}
		string lodName = GetLODName(m_strGeomFileNameNoExt, baseLOD + lodCount);
		CContentCGF* pChunkFile = g_pI3DEngine->CreateChunkfileContent(lodName);
		bool bLoaded = g_pI3DEngine->LoadChunkFileContent(pChunkFile, lodName,bNoWarnings);
		if (pChunkFile && bLoaded)
		{
			if (useAssetDefinedLOD && pChunkFile->GetNodeCount() > 0)
			{
				CNodeCGF* rootNode = pChunkFile->GetNode(0);
				if (strstr(rootNode->properties, "consoles_lod0") != 0)
					assetDefinedLOD = lodCount;
			}
			cgfs.arr.push_back(pChunkFile);
			lodCount++; 
		}
		else
		{
			delete pChunkFile; 
			break;
		}
	}

	if (assetDefinedLOD < baseLOD)
		assetDefinedLOD = baseLOD;

	int minLod = 0;
	static ICVar* p_e_char_lod_min = gEnv->pConsole->GetCVar("e_charLodMin");
	static ICVar* p_e_lod_min = gEnv->pConsole->GetCVar("e_LodMin");
	if (p_e_char_lod_min)
		minLod = p_e_char_lod_min->GetIVal();
	if (p_e_lod_min)
		minLod = max(minLod, p_e_lod_min->GetIVal());
	if(minLod > 0)
		baseLOD = minLod;

	if ( baseLOD >= lodCount)
		baseLOD = lodCount - 1;

	if (assetDefinedLOD >= lodCount - 1)
		assetDefinedLOD = lodCount - 1;

	// TODO: memory leak is possible when early 'return 0' is executed after a "bad data" Warning() call
	CCharacterModel* pModel = new CCharacterModel(strGeomFileName, pManager, CHR,cgfs.arr[0]->GetNode(0)->name);

	const char* fname = pModel->GetModelFilePath();

	pModel->m_nBaseLOD = baseLOD;
	pModel->m_arrModelMeshes.resize(lodCount, CModelMesh()); // we are going to remove unused meshes later

	IGeomManager *pPhysicalGeometryManager = g_pIPhysicalWorld ? g_pIPhysicalWorld->GetGeomManager() : NULL;
	assert(pPhysicalGeometryManager);


	// Create a remapping table for morph names to morph indices. This is so the morph with a given
	// name appears in the same position for all LODs (otherwise the wrong morph can get played if
	// morphs are missing in lower LODs).
	VectorMap<string, int> morphNameIndexMap;
	CreateMorphNameToIndexMap(morphNameIndexMap, &cgfs.arr[0], lodCount, filename);

	CContentCGF* pBaseContent = cgfs.arr[0];
	for (uint32 lod = 0; lod < lodCount; ++lod) 
	{
		CContentCGF* pLodContent = cgfs.arr[lod];
		if (pLodContent == 0)
			continue;
		CSkinningInfo* pSkinningInfo = pLodContent->GetSkinningInfo();
		if (pSkinningInfo == 0)
		{
			g_pISystem->Warning(VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE, strGeomFileName, "Skinning info is missing in lod %d", lod);
			return 0;
		}


		//--------------------------------------------------------------------------
		CMesh* pMesh = 0;
		_smart_ptr<IMaterial> pMaterial;
		FindFirstMeshAndMaterial(pMesh, pMaterial, pLodContent, m_strGeomFileNameNoExt.c_str(), lod);

		if (pMaterial) 
		{
			pModel->SetMaterial( pMaterial );
		}

		CModelMesh* pModelMesh = pModel->GetModelMesh(lod);
		pModelMesh->m_nLOD = lod;
		pModelMesh->m_pModel = pModel;

#if !(defined(XENON) || defined(PS3))
		//compare bone between different LODs
		if (lod >= 1) 
		{ 
			CSkinningInfo* pBaseSkinning = pBaseContent->GetSkinningInfo();
			CSkinningInfo* pLodSkinning = pLodContent->GetSkinningInfo();
			if (!ValidateLodSkeleton(pBaseSkinning, pLodSkinning, lod, filename))
				return 0;
		}
#endif

		if (lod==0) 
		{
			//-----------------------------------------------------------------
			//---                initialize bone info                       ---
			//---  in LOD 0 we initialize the bones and the morph-targets,  ---
			//---  in LOD 1 we update their physics; 
			//---  LOD 2 has no effect
			//-----------------------------------------------------------------
			if (Console::GetInst().ca_UseDecals)
				pModel->m_arrCollisions.reserve(pSkinningInfo->m_arrBonesDesc.size());

			if (!InitializeBones(&pModel->m_ModelSkeleton, pSkinningInfo, filename))
				return 0;

		} //if(lod==0)

		//in LOD 1 we update the physics; lod 2 has no effect
		if (lod<2) 
			pModel->m_ModelSkeleton.m_arrModelJoints[0].UpdateHierarchyPhysics( pSkinningInfo->m_arrBoneEntities, lod );


		//-----------------------------------------------------------------------------------
		//-----------------------------------------------------------------------------------
		if (pPhysicalGeometryManager) 
		{
			if (!SetupPhysicalProxies(pPhysicalGeometryManager, pModel, pMaterial, pSkinningInfo, strGeomFileName.c_str(), lod))
				return 0;
		}

		const char* RootName = pModel->m_ModelSkeleton.m_arrModelJoints[0].GetJointName();
		if (RootName[0]=='B' && RootName[1]=='i' && RootName[2]=='p' && RootName[3]=='0' && RootName[4]=='1')
		{
			//This character-model is a biped and it was made in CharacterStudio. 
			//CharacterStudio is not allowing us to project the Bip01 on the ground and change its orientation; thats why we have to do it here.
			pModel->m_ModelSkeleton.m_poseData.m_pJointsAbsolute[0].SetIdentity();
			pModel->m_ModelSkeleton.m_poseData.m_pJointsRelative[0].SetIdentity();
			uint32 numJoints = pModel->m_ModelSkeleton.m_arrModelJoints.size();	
			for (uint32 bone=1; bone<numJoints; bone++)
			{
				//adjust the pelvis (and maybe multiple-root hierarchies, which are used in CGAs.)
				int32 idxParent = pModel->m_ModelSkeleton.m_arrModelJoints[bone].m_idxParent;
				if (idxParent==0)
					pModel->m_ModelSkeleton.m_poseData.m_pJointsRelative[bone] =		pModel->m_ModelSkeleton.m_poseData.m_pJointsAbsolute[bone];
			}
		}

		QuatT orientation = pModel->m_ModelSkeleton.m_poseData.m_pJointsAbsolute[0];
		Vec3 cx=orientation.GetColumn0();
		Vec3 cy=orientation.GetColumn1();
		Vec3 cz=orientation.GetColumn2();
		uint32 IsIdentiy = Quat::IsEquivalent(orientation.q, Quat(IDENTITY),0.01f);
		if (IsIdentiy==0)
		{
			string LODFileName = GetLODName(m_strGeomFileNameNoExt, lod);					
			g_pISystem->Warning( VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE,LODFileName,	"Default-Skeleton has wrong orientation. Please use the CryEngine conventions when building a model" );
			//CRY_ASSERT_MESSAGE(!"Inverted model orientation!", strGeomFileName);
			return 0;
		}


		if (pModelMesh)
		{
			//--------------------------------------------------------------------------
			//---                copy internal skin-vertices                         ---
			//--------------------------------------------------------------------------
			uint32 numIntVertices = pSkinningInfo->m_arrIntVertices.size();
			assert(numIntVertices);
			if (numIntVertices <= 0)
			{
				g_pISystem->Warning(VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE, strGeomFileName, "No internal vertices in lod %d", lod);
				return 0;
			}
			pModelMesh->m_arrIntVertices=pSkinningInfo->m_arrIntVertices;

			//--------------------------------------------------------------------------
			//---                     copy Ext2Int map                               ---
			//--------------------------------------------------------------------------
			pModelMesh->m_numExtVertices = pSkinningInfo->m_arrExt2IntMap.size();
			assert(pModelMesh->m_numExtVertices);
			if (pModelMesh->m_numExtVertices <= 0)
			{
				g_pISystem->Warning(VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE, strGeomFileName, "Ext2Int map is empty in lod %d", lod);
				return 0;
			}


			SetupShapeDeformation(pModelMesh, pMesh, pSkinningInfo->m_arrExt2IntMap, numIntVertices, filename, lod);

			IMaterial *pIMaterial = pModel->GetMaterial();
			assert(pIMaterial);
			if (pIMaterial == 0)
			{
				g_pISystem->Warning(VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE, strGeomFileName, "No material in lod %d", lod);
				return 0;
			}

			//HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-
			//fix this in the exporter
			uint32 numSubsets = pMesh->m_subsets.size();
			for (uint32 i=0; i<numSubsets; i++)	
			{
				pMesh->m_subsets[i].FixRanges(pMesh->m_pIndices);
			}


			uint32 numIntFaces = pSkinningInfo->m_arrIntFaces.size();
			uint32 numExtFaces = pMesh->m_nIndexCount/3;
			assert(numIntFaces);
			if (numIntFaces <= 0)
			{
				g_pISystem->Warning(VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE, strGeomFileName, "No internal faces in lod %d", lod);
				return 0;
			}

			if (numExtFaces!=numIntFaces) 
			{
				g_pISystem->Warning( VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE, filename, "external and internal face-amount is different. Probably caused by degenerated faces" );
			}


			// TEMP: Convert tangent frame from matrix to quaternion based.
			// This code should go into RC and not be performed at loading time!
			if (pMesh->m_pTangents)
			{
				if (!ConvertTangentsToQTangents(pMesh))
					return 0;
			}

			// Indices in CollisionBBoxes in internal format!!!
			if (Console::GetInst().ca_UseDecals)
			{
				bool isBaseLod = lod == baseLOD;

				if (!SetupCollisionInfo(pModel, pModelMesh, pSkinningInfo, pSkinningInfo->m_arrExt2IntMap, isBaseLod))
					return 0;
			}

			//--------------------------------------------------------------------------
			//---           copy morph-targets are just for LOD 0                    ---
			//--------------------------------------------------------------------------
			if (pSkinningInfo->m_arrMorphTargets.size() != 0)
			{
				DynArray<ExtSkinVertex> arrExtVertices;
				if (!ExtractExternalVertices(arrExtVertices, pModelMesh, pMesh, filename, lod))
					return 0;

				if (!CopyMorphTargets(pModelMesh, pSkinningInfo, arrExtVertices, morphNameIndexMap, filename, lod))
					return 0;
			}

			//////////////////////////////////////////////////////////////////////////
			// Create the RenderMesh
			if (int(lod) >= pModel->m_nBaseLOD )
			{
				const char* pName = pModel->m_strFilePath;
				uint32 numSubsets2 = pMesh->GetSubSetCount();
				for (uint32 s=0; s<numSubsets2; s++)
				{
					uint32 numID = pMesh->m_subsets[s].m_arrGlobalBonesPerSubset.size();
					for(uint32 d=0; d<numID; d++)
					{
						uint16 id = uint16(pMesh->m_subsets[s].m_arrGlobalBonesPerSubset[d]);
						if (id>255)
							CryFatalError("CryAnimation: Invalid Chunk-ID in Model: %s  lod: %d",pName,lod);
					}
				}

				pModelMesh->m_numExtTriangles = pMesh->m_nIndexCount/3;
				if (lod >= assetDefinedLOD)
				{
					uint32 renderLOD = lod - assetDefinedLOD;
					pModel->m_pRenderMeshs[renderLOD] =	g_pIRenderer->CreateRenderMesh("Character", pModel->GetModelFilePath());
					assert(pModel->m_pRenderMeshs[renderLOD] != 0);
					if (pModel->m_pRenderMeshs[renderLOD] == 0)
					{
						g_pISystem->Warning(VALIDATOR_MODULE_ANIMATION, VALIDATOR_WARNING, VALIDATOR_FLAG_FILE, strGeomFileName, "Failed to create render mesh for lod %d", lod);
						return 0;
					}

					// CreateRenderMesh create an object with an refcount of 1, by using a smart-ptr the refcount is incrased to 2, so use Release to correct the refcount
					pModel->m_pRenderMeshs[renderLOD]->Release();			
					pModel->m_pRenderMeshs[renderLOD]->SetMesh( *pMesh, 0, FSM_MORPH_TARGETS | FSM_CREATE_DEVICE_MESH | FSM_SUPPORT_HW_TESSELLATION, 0);
				}
			}
			else
				pModelMesh->m_numExtTriangles = 0;

			if (Console::GetInst().ca_UseDecals && lod == baseLOD) 
			{
				if (!FinishSetupCollisionInfo(pModel, pMesh, pSkinningInfo, filename))
					return 0;
			}
		}
	} //loop over all LODs

	//---------------------------------------------------------------------------------------------------------------

	cgfs.release();

	if (assetDefinedLOD != 0)
	{
		// we don't have render-meshes for these meshes.
		size_t eraseAfter = pModel->m_arrModelMeshes.size() - assetDefinedLOD;
		pModel->m_arrModelMeshes.erase(pModel->m_arrModelMeshes.begin() + eraseAfter, pModel->m_arrModelMeshes.end());
	}

	pModel->m_ModelSkeleton.SetIKJointID();

	//---------------------------------------------------------------------------------------------------------------

	//////////////////////////////////////////////////////////////////////////
	//use vertices to calculate AABB
	CModelMesh* pModelMesh = pModel->GetModelMesh(baseLOD);

	if (Console::GetInst().ca_UseDecals)
	{
		pModel->m_ModelAABB=AABB(Vec3(VMAX),Vec3(VMIN));
		pModel->m_ModelAABB = pModel->m_arrCollisions[0].m_aABB;//
	}

	//////////////////////////////////////////////////////////////////////////
	
	pModel->m_ModelSkeleton.PrepareJointIDHash();

	m_pModel = pModel;

	stack_string paramFileName = m_strGeomFileNameNoExt + "." + CRY_CHARACTER_PARAM_FILE_EXT;

	LoadParams(pModel, paramFileName, IsSkinAttachment);

	return pModel;
}

void CryCHRLoader::LoadFaceLib( const char* faceLibFile, const char* animDirName, CCharacterModel* pModel )
{
	LOADING_TIME_PROFILE_SECTION(g_pISystem);
	// Facial expressions library.
	_smart_ptr<IFacialEffectorsLibrary> pLib;		

	pLib = g_pCharacterManager->GetIFacialAnimation()->LoadEffectorsLibrary(faceLibFile);
	if (!pLib)
	{
		// Search in animation directory .chr file directory.
		pLib = g_pCharacterManager->GetIFacialAnimation()->LoadEffectorsLibrary(
			PathUtil::Make(animDirName, faceLibFile));
	}
	if (!pLib)
	{
		// Search in .chr file directory.					
		pLib = g_pCharacterManager->GetIFacialAnimation()->LoadEffectorsLibrary(
			PathUtil::Make(PathUtil::GetParentDirectory(m_strGeomFileNameNoExt), faceLibFile));
	}
	if (pLib)
	{
		if (pModel->GetFacialModel())
		{
			pModel->GetFacialModel()->AssignLibrary(pLib);
		}
	}
	else
	{
		AnimWarning("Facial Effector Library %s not found (when loading %s.cal)", faceLibFile, m_strGeomFileNameNoExt.c_str());
	}
}

//-------------------------------------------------------------------------------------------------
#define MAX_STRING_LENGTH (256)

uint32 CryCHRLoader::ReuseAnimationFiles(CParamLoader& paramLoader, uint32 listID)
{
	LOADING_TIME_PROFILE_SECTION(g_pISystem);

	const SAnimListInfo& animList = paramLoader.GetParsedList(listID);
	int32 numAnims = animList.arrLoadedAnimFiles.size();
	uint32 result = 0;

	CCharacterModel *pModel = m_pModel;

	for (int i = 0; i < numAnims; ++i)
	{
		const ModelAnimationHeader& animHeader = animList.arrLoadedAnimFiles[i];

		SAnimListInfo::StandupAnimsDataMap::const_iterator itFind = animList.standupAnimsData.find(animHeader.m_CRC32Name);
		const char* szStandupAnimType = (itFind == animList.standupAnimsData.end()) ? NULL : itFind->second.c_str();

		pModel->m_AnimationSet.ReuseAnimation(animHeader, szStandupAnimType);
		result++;
	}

	return result;
}

uint32 CryCHRLoader::LoadAnimationFiles(CParamLoader& paramLoader, uint32 listID) 
{
	LOADING_TIME_PROFILE_SECTION(g_pISystem);
	// go through all Anims on stack and load them
	uint32 nAnimID = 0;
	int32 numAnims = 0;

	CCharacterModel *pModel = m_pModel;
	const SAnimListInfo& animList = paramLoader.GetParsedList(listID);

	numAnims = animList.arrAnimFiles.size();

	for (int i=0; i<numAnims; i++)
	{
		const char* pFilePath = animList.arrAnimFiles[i].m_FilePathQ;
		const char* pAnimName = animList.arrAnimFiles[i].m_AnimNameQ;
		const char* fileExt = PathUtil::GetExt(pFilePath);

		bool IsBAD = (0 == stricmp(fileExt,""));
		if (IsBAD)
			continue;

		bool IsCHR = (0 == stricmp(fileExt,"chr"));
		if (IsCHR)
			continue;
		bool IsCDF = (0 == stricmp(fileExt,"cdf"));
		if (IsCDF)
			continue;
		bool IsCGA = (0 == stricmp(fileExt,"cga"));
		if (IsCGA)
			continue;

		uint32 IsCAF = (0 == stricmp(fileExt,"caf"));
		uint32 IsLMG = (0 == stricmp(fileExt,"lmg"));
		uint32 IsAAC = (0 == stricmp(fileExt,"caa"));
		assert(IsCAF+IsLMG+IsAAC);

		bool IsAimPose = 0;
		bool IsLookPose = 0;
		if (IsCAF)
		{
			uint32 numAimDB = m_pModel->m_ModelSkeleton.m_AimDirBlends.size();
			for (uint32 d=0; d<numAimDB; d++)
			{
				const char* strAimIK_Token = m_pModel->m_ModelSkeleton.m_AimDirBlends[d].m_AnimToken;
				IsAimPose = (CryStringUtils::stristr(pAnimName,strAimIK_Token) != 0);
				if (IsAimPose)
					break;
			}
			uint32 numLookDB = m_pModel->m_ModelSkeleton.m_LookDirBlends.size();
			for (uint32 d=0; d<numLookDB; d++)
			{
				const char* strLookIK_Token = m_pModel->m_ModelSkeleton.m_LookDirBlends[d].m_AnimToken;
				IsLookPose = (CryStringUtils::stristr(pAnimName,strLookIK_Token) != 0);
				if (IsLookPose)
					break;
			}
		}

		if (IsCAF && IsAimPose)
			IsCAF=0;
		if (IsCAF && IsLookPose)
			IsCAF=0;

		// ben: it can happen that a AnimID is defined in a list and in one of its dependencies that
		// was included after the definition of that animation, this is a different behavior than
		// before, so errors might pop up that did not pop up before 

		bool bStandupAnim = false;
		CryFixedStringT<256> standupAnimType;

		int32 nGlobalAnimID=-1;
		if (IsCAF)
		{
			nGlobalAnimID = pModel->m_AnimationSet.LoadFileCAF( pFilePath, pAnimName );

			bStandupAnim = pModel->m_AnimationSet.IsStandupAnim(pAnimName, standupAnimType);
		}
		else if (IsAimPose || IsLookPose)
			nGlobalAnimID = pModel->m_AnimationSet.LoadFileAIM( pFilePath, pAnimName );
		else if (IsLMG)
			nGlobalAnimID = pModel->m_AnimationSet.LoadFileLMG( pFilePath, pAnimName );

		int nAnimID0 = pModel->m_AnimationSet.GetAnimIDByName( pAnimName );
		if (nAnimID0>=0)
		{
			ModelAnimationHeader* pAnim = (ModelAnimationHeader*)pModel->m_AnimationSet.GetModelAnimationHeader(nAnimID0);
			if (pAnim)
			{
				nAnimID++;

		    uint32 numAimPoses = animList.arrAnimFiles[i].m_arrAimPoses.size();
		    if (numAimPoses)
		    {
					//assert(arrAnimFiles[i]->arrAimPoses.size()>numAimPoses);
					assert(pAnim!=0);
					for (uint32 a=0; a<numAimPoses; a++)
						PREFAST_SUPPRESS_WARNING(6386) pAnim->m_strAimPose1[a] = animList.arrAnimFiles[i].m_arrAimPoses[a].c_str();
				}

				// store ModelAnimationHeader to reuse it in the case that a second model will need it
				paramLoader.AddLoadedHeader(listID, *pAnim);

				if (bStandupAnim)
					paramLoader.AddStandupAnimData(listID, pAnim->m_CRC32Name, standupAnimType.c_str());
			}	
		}
		else
		{
			AnimWarning("Animation (caf) file \"%s\" could not be read (it's an animation of \"%s.%s\")", animList.arrAnimFiles[i].m_FilePathQ, m_strGeomFileNameNoExt.c_str(),CRY_CHARACTER_FILE_EXT );
		}
	}

	return nAnimID;
}

//------------------------------------------------------------------------------------------------------------
// loads animations for a model by composing a complete animation list and loading it into an AnimationSet
//------------------------------------------------------------------------------------------------------------
bool CryCHRLoader::LoadAnimations(CParamLoader& paramLoader)
{
	LOADING_TIME_PROFILE_SECTION(g_pISystem);

	CCharacterModel* pModel = m_pModel;

	bool bParseSubFolders = false;

	// the number of animations loaded   
	uint32 numAnimAssets = 0;

	int32 numAnims = 0;

	FUNCTION_PROFILER( gEnv->pSystem, PROFILE_ANIMATION );

	if (m_animListIDs.size() == 0)
	{
		return false;
	}

	// attributes of the compound list
	// all those attributes have to be stored on a per list basis,
	// keeping it for the topmost list in memory is not enough
	// since another list might use it as a dependency which does not
	// override it
	DynArray<string> modelTracksDatabases;
	DynArray<string> lockedDatabases;
	string animEventDatabase;
	string faceLibFile;
	string faceLibDir;
	CAnimationSet::FacialAnimationSet::container_type facialAnimations;

	int32 nListIDs = m_animListIDs.size();
	for (int32 i = nListIDs-1; i >= 0; --i)
	{
		const SAnimListInfo& animList = paramLoader.GetParsedList(m_animListIDs[i]);
		numAnims += animList.arrAnimFiles.size();
		modelTracksDatabases.push_back(animList.modelTracksDatabases);
		lockedDatabases.push_back(animList.lockedDatabases);

		if (animEventDatabase.empty())
		{
			animEventDatabase = animList.animEventDatabase;
		}

		if (faceLibFile.empty())
		{
			faceLibFile = animList.faceLibFile;
			faceLibDir = animList.faceLibDir;
		}

		uint32 numAnimNames = animList.arrAnimFiles.size();
	}

	pModel->m_AnimationSet.prepareLoadCAFs ( numAnims );

	uint32 numTracksDataBases = modelTracksDatabases.size();
	if (numTracksDataBases)
		g_AnimationManager.CreateGlobalHeaderDBA(modelTracksDatabases);

	// Lock Persistent Databases
	DynArray<string>::iterator it;
	for (it = lockedDatabases.begin(); it != lockedDatabases.end(); ++it)
	{
		g_AnimationManager.DBA_LockStatus(it->c_str(), 1, false);
	}

	if (!animEventDatabase.empty())
	{
		pModel->SetModelAnimEventDatabase(animEventDatabase);
	}

	if (!faceLibFile.empty())
	{
		pModel->CreateFacialInstance();
		LoadFaceLib(faceLibFile, faceLibDir, pModel);
	}

	for (int32 i = nListIDs-1; i >= 0; --i)
	{
		const SAnimListInfo& animList = paramLoader.GetParsedList(m_animListIDs[i]);
		uint32 numAnimNames = animList.arrAnimFiles.size();

		// this is where the Animation List Cache kicks in
		if (!animList.headersLoaded)
		{
		  paramLoader.ExpandWildcards(m_animListIDs[i],  bParseSubFolders);
			numAnimAssets += LoadAnimationFiles(paramLoader, m_animListIDs[i]);
			// this list has been processed, dont do it again!
			paramLoader.SetHeadersLoaded(m_animListIDs[i]);
		}
		else
		{
			numAnimAssets += ReuseAnimationFiles(paramLoader, m_animListIDs[i]);
		}

		if (!animList.facialAnimations.empty())
		  facialAnimations.insert(facialAnimations.end(), animList.facialAnimations.begin(), animList.facialAnimations.end());
	}

		// Store the list of facial animations in the model animation set.
		if (!facialAnimations.empty())
			pModel->m_AnimationSet.GetFacialAnimations().SwapElementsWithVector(facialAnimations);

	AnimEventLoader::loadAnimationEventDatabase(pModel, pModel->GetModelAnimEventDatabaseCStr());

	uint32 dddd = g_AnimationManager.m_arrGlobalCAF.size();  

	if (numAnimAssets > 1)
		g_pILog->UpdateLoadingScreen("  %d animation-assets loaded (total assets: %d)", numAnimAssets, g_AnimationManager.m_arrGlobalCAF.size());

	if (Console::GetInst().ca_MemoryUsageLog)
	{
		CryModuleMemoryInfo info;
		CryGetMemoryInfoForModule(&info);
		g_pILog->UpdateLoadingScreen("Memstat %i", (int)(info.allocated - info.freed));
	}

	pModel->m_AnimationSet.VerifyLMGs();

	// If there is a facial model, but no expression library, we should create an empty library for it.
	// When we assign the library to the facial model it will automatically be assigned the morphs as expressions.
	{
		CFacialModel* pFacialModel = (pModel ? pModel->GetFacialModel() : 0);
		CFacialEffectorsLibrary* pEffectorsLibrary = (pFacialModel ? pFacialModel->GetFacialEffectorsLibrary() : 0);
		if (pFacialModel && !pEffectorsLibrary)
		{
			CFacialAnimation* pFacialAnimationManager = (g_pCharacterManager ? g_pCharacterManager->GetFacialAnimation() : 0);
			CFacialEffectorsLibrary* pNewLibrary = new CFacialEffectorsLibrary(pFacialAnimationManager);
			const string& filePath = pModel->GetFilePath();
			if (!filePath.empty())
			{
				string libraryFilePath = PathUtil::ReplaceExtension(filePath, ".fxl");
				pNewLibrary->SetName(libraryFilePath);
			}
			if (pFacialModel && pNewLibrary)
				pFacialModel->AssignLibrary(pNewLibrary);
		}
	}

	if (Console::GetInst().ca_PrecacheAnimationSets)
	{
		CAnimationSet& animSet = pModel->m_AnimationSet;
		uint32 animCount = animSet.GetAnimationCount();
		for(uint32 i = 0; i < animCount; ++i)
		{
			const ModelAnimationHeader* pHeader = animSet.GetModelAnimationHeader(i);
			if (pHeader)
			{
				if (pHeader->m_nAssetType == CAF_File)
				{
					uint32 globalID = pHeader->m_nGlobalAnimId;
					if (globalID > 0)
						g_AnimationManager.m_arrGlobalCAF[globalID].StartStreamingCAF();
				}
			}
		}
	}

	CModelMesh* pModelMesh = pModel->GetModelMesh(0);
	return  pModel->m_ModelSkeleton.m_arrModelJoints.size() || pModelMesh->m_morphTargets.size();
}

// cleans up the resources allocated during load
void CryCHRLoader::clear()
{
	m_strGeomFileNameNoExt = "";
	m_animListIDs.clear();
}


const string CryCHRLoader::GetDefaultAnimDir()
{
	stack_string animDirName = PathUtil::GetParentDirectory(m_strGeomFileNameNoExt, 3);
	if (!animDirName.empty())
		return animDirName += "/animations";
	else
		return animDirName += "animations";
}

void CryCHRLoader::LoadParams(CCharacterModel* pModel, const char* paramFileName, uint32 IsSkinAttachment)
{
	m_pModel = pModel;

	// If the new .params file exists, load it since it can be done much faster and the file is cleaner (all XML)
	if (g_pIPak->IsFileExist(paramFileName))
	{
		CParamLoader& paramLoader = g_pCharacterManager->GetParamLoader();
		bool ok = paramLoader.LoadXML(pModel, GetDefaultAnimDir(), paramFileName,IsSkinAttachment, m_animListIDs);
		if(ok)
			LoadAnimations(paramLoader);
	}
}
